#pragma one

#include <vector>
#include <queue>
#include "Mutex.hpp"
#include "Task.hpp"
#include "Cond.hpp"
#include "MyThread.hpp"
#include "Logger.hpp"

#define PthreadNumDefault 3

//单例模式--懒汉模式
template<class T>
class ThreadPool
{
private:
    bool QueueIsEmpty()
    {
        return _q.empty();
    }

    void Routine(const std::string& name)
    {
        while(true)
        {
            T t;
            {
                LockGuard lockguard(&_lock);
                while (QueueIsEmpty() && _is_running)
                {
                    ++_cond_wait_num;
                    _cond.Wait(_lock);
                    --_cond_wait_num;
                }
                if (!_is_running && QueueIsEmpty())
                {
                    LOG(Loglevel::INFO) << " 线程池退出 && 任务队列为空, " << name << " 退出";
                    break;
                }
                t = _q.front();
                _q.pop();
            }
            t(); // 已经由公有（任务队列）变成私有（线程自己的）了，无需在锁内部执行了
            LOG(Loglevel::DEBUG) << name << " handler task: " << t.Result2String();

        }
    }

    ThreadPool(int threadnum = PthreadNumDefault)
        :_thread_num(threadnum)
        ,_cond_wait_num(0)
        ,_is_running(false)
    {
        for(int i = 0; i < threadnum; i++)
        {
            //假设线程初始化所用的任务类型为using func_t = std::function<void()>;这时如果我们想让线程执行的函数时void hello（）
            //但是这个函数却位于ThreadPool类中，这时候由于类中函数有一个this指针，而func_t中却没有，这时第一个想到的肯定是static
            //但是这样hello函数里的内容就无法访问ThreadPool中的私有成员了，不方便，这时候我们应该想到的一定是减少参数
            //方法一：auto f = std::bind(hello,this),将this指针绑定为第一个参数，这时f()传递参数时就变成无参了
            //方法二：用lambda表达式[this]()->void{this->hello()}
            std::string name = "thread-" + std::to_string(i + 1);
            _threads.emplace_back([this](const std::string &name)
                                 { this->Routine(name); }, name);
        }
        LOG(Loglevel::INFO) << "thread pool obj create success";
    }

    ThreadPool<T>& operator=(const ThreadPool<T>& tp) = delete;//单例模式，将拷贝构造和赋值重载禁用
    ThreadPool(const ThreadPool<T>& tp) = delete;

public:
    void Start()
    {
        //std::cout<<"Start进来了" << std::endl;
        if(_is_running)
            return;
        _is_running = true;
        for(auto& thread:_threads)
        {
            //std::cout<<"线程启动中" << std::endl;
            thread.Start();
        }
        LOG(Loglevel::INFO) << "thread pool running success";
    }

    void Stop()
    {
        if(!_is_running)
            return;
        _is_running = false;
        if(_cond_wait_num)
        {
            _cond.NotifyAll();//唤醒所有等待着的线程，让他们把任务执行完再退出
        }
    }

    void Wait()
    {
        for(auto& thread:_threads)
        {
            thread.Join();
        }
        LOG(Loglevel::INFO) << "thread pool wait success";
    }

    void Enqueue(const T& t)
    {
        if(!_is_running)
            return;
        {
            LockGuard lockguard(&_lock);
            _q.push(t);
            if(_cond_wait_num > 0)
            {
                _cond.NotifyOne();
            }
        }
    }

    static std::string ToHex(ThreadPool<T> *addr)//获取单例线程池的地址，就是为了测试是不是始终是一个地址
    {
        char buffer[64];
        snprintf(buffer, sizeof(buffer), "%p", addr);
        return buffer;
    }
    //这里用static并不是因为_instance是static的，而是因为我们在main中想调用这个函数，就需要指明对象，但是构造函数已经被设置
    //为了私有的，所以我们把他设置为static使他可以不用对象就可以被调用
    static ThreadPool<T>* GetInstance()
    {
        if(!_instance)//创建完线程池后，不能每次获取地址都要让线程竞争一次锁吧，这要可以保证下次地址存在就直接获取了地址
        //虽然第一次的时候可能还是所有线程都会进去，但至少保证了后几次提高了效率
        {
            //我们这里创建单例线程池时只有一个主线程（进程）创建，所以这个锁其实是没有必要的，包括后面我们获取这个地址也
            //只有主线程来获取，那为什么我还要写上这把锁呢？因为万一是多线程创建单例线程池呢（现在是一对多，万一有多对多呢）
            LockGuard lockguard(&_sigleton_lock);
            if (!_instance)
            {
                _instance = new ThreadPool<T>();
                LOG(Loglevel::DEBUG) << "线程池单例首次被使用，创建并初始化, addr: " << ToHex(_instance);
                _instance->Start();
            }
        }
        return _instance;
    }
private:
    std::queue<T> _q;
    std::vector<Thread> _threads;

    bool _is_running;
    int _thread_num;
    int _cond_wait_num;

    Mutex _lock;//用来给访问任务队列加锁
    Cond _cond;
    static Mutex _sigleton_lock;//用来给初始化线程池加锁,并且该锁只有一把

    static ThreadPool<T>* _instance;//单例模式，设置为static，让所有的线程池构造只能有一个对象（静态变量位所有对象所共享）
};

template<class T>
ThreadPool<T>* ThreadPool<T>::_instance = nullptr;//标明作用域

template<class T>
Mutex ThreadPool<T>::_sigleton_lock;
